#!/usr/bin/env python
# -*- coding: utf-8
#pico.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2010 Aleksey Sadovoy <lex@progger.ru>
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

import ctypes
import os
import sys

#import characterProcessing
#from synthDriverHandler import SynthDriver,VoiceInfo
#from logHandler import log
    
import speechDictHandler

SVOX_MEMORY_SIZE=3*1024**2
OUT_BUFFER_SIZE=4096 #it really generates 64 bytes at once
#from picoapi.h
pico_system=pico_resource=pico_engine=ctypes.c_void_p
PICO_STEP_IDLE=200
PICO_STEP_BUSY=201

class SynthDriver():
	name = "pico"
	description = "Svox pico synthesizer"
	
#	availableVoices=(
#		VoiceInfo('en-us',_('American English')),
#		VoiceInfo('en-gb',_('British English')),
#		VoiceInfo('es',_('Spanish')),
#		VoiceInfo('fr',_('French')),
#		VoiceInfo('it',_('Italian')),
#		VoiceInfo('de',_('Deutch')),
#	)
	_voice='fr'
	pitch=50
	rate=50
	volume=100
	jobdone=False

	#:tuples of (langName,langData,speakerData)
	voice_resources={
		'en-us': ('American English', 'en-US_ta.bin', 'en-US_lh0_sg.bin'),
		'en-gb': ('British English', 'en-GB_ta.bin','en-GB_kh0_sg.bin'),
		'es': ('Spanish', 'es-ES_ta.bin', 'es-ES_zl0_sg.bin'),
		'fr': ('French','fr-FR_ta.bin','fr-FR_nk0_sg.bin'),
		'it': ('Italian', 'it-IT_ta.bin','it-IT_cm0_sg.bin'),
		'de': ('Deutch', 'de-DE_ta.bin', 'de-DE_gl0_sg.bin'),
	}

	@classmethod
	def check(cls):
		return os.path.isfile(os.sep.join([self.base_path,"svox-pico.dll"]))

	def pico_system_errcheck(self,result,func,args):
		if result!=0:
			message=ctypes.create_string_buffer(200)
			self.dll.pico_getSystemStatusMessage(self.pico_system,result,message)
			raise RuntimeError("error while calling '"+func.__name__)
		return result

	def pico_engine_errcheck(self,result,func,args):
		if result<0:
			message=ctypes.create_string_buffer(200)
			self.dll.pico_getEngineStatusMessage(self.pico_engine,result,message)
			raise RuntimeError("error while calling '"+func.__name__)
		return result

	def __init__(self, bpath = None):
		self.base_path = bpath
		if self.base_path is None:
			self.base_path = os.path.dirname(os.path.abspath(__file__))

		self.dll=ctypes.cdll.LoadLibrary(os.sep.join([self.base_path,'svox-pico.dll']))
		#prepare dll object
		system_functs=('pico_initialize','pico_terminate','pico_getSystemStatusMessage','pico_getNrSystemWarnings',
		'pico_getSystemWarning','pico_loadResource','pico_unloadResource','pico_getResourceName','pico_createVoiceDefinition','pico_addResourceToVoiceDefinition',
		'pico_releaseVoiceDefinition','pico_newEngine','pico_disposeEngine')
		for func in system_functs:
			getattr(self.dll,func).errcheck=self.pico_system_errcheck
		engine_funcs=('pico_putTextUtf8','pico_getData','pico_resetEngine','pico_getEngineStatusMessage','pico_getNrEngineWarnings','pico_getEngineWarning')
		for func in engine_funcs:
			getattr(self.dll,func).errcheck=self.pico_engine_errcheck
		#init pico system
		self._svox_memory=ctypes.create_string_buffer(SVOX_MEMORY_SIZE)
		self.pico_system=pico_system()
		self.dll.pico_initialize(self._svox_memory, SVOX_MEMORY_SIZE, ctypes.byref(self.pico_system))
		self.pico_engine=None
		self.isSpeaking=False
		#log the version
		#version_string=ctypes.create_string_buffer(200)
		#self.dll.picoext_getVersionInfo(version_string,200)
		#log.info("Using pico version '%s'"%version_string.value)

	def load_resources(self,name,langData,speakerData):
		"""Loads lingware data, defines voice"""
		langRes=pico_resource()
		fpath = os.sep.join([self.base_path,'svox-pico-data',langData])
		fpath = fpath.encode('utf-8')
		self.dll.pico_loadResource(self.pico_system,fpath,ctypes.byref(langRes))
		langResName=ctypes.create_string_buffer(200)
		status= self.dll.pico_getResourceName(self.pico_system,langRes,langResName)
		speakerRes=pico_resource()

		fpath = os.sep.join([self.base_path,'svox-pico-data',speakerData])
		fpath = fpath.encode('utf-8')
		self.dll.pico_loadResource(self.pico_system,fpath,ctypes.byref(speakerRes))
		speakerResName=ctypes.create_string_buffer(200)
		self.dll.pico_getResourceName(self.pico_system,speakerRes,speakerResName)
		self.dll.pico_createVoiceDefinition(self.pico_system,name)
		self.dll.pico_addResourceToVoiceDefinition(self.pico_system,name,langResName)
		self.dll.pico_addResourceToVoiceDefinition(self.pico_system,name,speakerResName)
		self._resources=(name,langRes,speakerRes)
		
		self.pico_engine=pico_engine()
		self.dll.pico_newEngine(self.pico_system,name,ctypes.byref(self.pico_engine))
		self._voice=name
	
	def free_resources(self):
		if not self._resources: return
		self.dll.pico_releaseVoiceDefinition(self.pico_system,self._resources[0])
		self.dll.pico_unloadResource(self.pico_system,ctypes.byref(self._resources[1]))
		self.dll.pico_unloadResource(self.pico_system,ctypes.byref(self._resources[2]))
		self._resources=None

	def terminate(self):
		if self.pico_engine:
			self.dll.pico_disposeEngine(self.pico_system,ctypes.byref(self.pico_engine))
		self.free_resources()
		self.dll.pico_terminate(ctypes.byref(self.pico_system))
		self.pico_system=None
		del self.dll

	def _get_voice(self):
		return self._voice

	def _set_voice(self,value):
		#name=str(self.getVoiceInfoByID(value).name)
		name=value
		if self.pico_engine:
			self.isSpeaking=False
			self.dll.pico_disposeEngine(self.pico_system,ctypes.byref(self.pico_engine))
			self.free_resources()
		#self.load_resources(*self.voice_resources[value])
		self.load_resources('French','fr-FR_ta.bin','fr-FR_nk0_sg.bin')
		self.pico_engine=pico_engine()
		self.dll.pico_newEngine(self.pico_system,name,ctypes.byref(self.pico_engine))
		self._voice=value

	def build_string(self,s):
		"""applies voice parameters"""
		pitch=self.pitch+50 if self.pitch<=50 else self.pitch*2
		speed= int(20+(self.rate/50.0)*80) if self.rate<=50 else 100+(self.rate-50)*8
		volume=80
		return '<pitch level="%d"><speed level="%d"><volume level="%d">%s</volume></speed></pitch>'%(pitch,speed,volume,s)
		
	def stop(self):
		self.isSpeaking=False

	def speak_text(self, data):
		bytes_sent=ctypes.c_int16()
		out_buffer=ctypes.create_string_buffer(OUT_BUFFER_SIZE)
		bytes_received=ctypes.c_int16()
		data_type=ctypes.c_int16()
		self.isSpeaking = True

		index=None
		data = data.encode('utf-8')
		if data is None:
			return
		self.lastIndex=index
		remaining=len(data)+1
		while remaining and self.isSpeaking:
			self.dll.pico_putTextUtf8(self.pico_engine,data,remaining,ctypes.byref(bytes_sent))
			remaining -= bytes_sent.value
			data = data[bytes_sent.value:]
			status = PICO_STEP_BUSY
			while self.isSpeaking and status == PICO_STEP_BUSY:
				status=self.dll.pico_getData(self.pico_engine,out_buffer,OUT_BUFFER_SIZE,ctypes.byref(bytes_received),ctypes.byref(data_type))
		self.lastIndex = None
		self.jobdone = True
		self.dll.pico_resetEngine(self.pico_engine,0)
		self.terminate()

#synthd = SynthDriver()
#synthd.load_resources('French','fr-FR_ta.bin','fr-FR_nk0_sg.bin')
#speechDictHandler.initialize()
if __name__ == "__main__":
	import winsound
	
	data1 = '<genfile file="temp.wav">bonjour</genfile>'
	langue = 'fr'
	synthd = SynthDriver()
	synthd.load_resources(*synthd.voice_resources['fr'])
	synthd.rate = int(50)
	speechDictHandler.initialize('fr')
	data1 = speechDictHandler.processText(data1)
	synthd.speak_text(synthd.build_string(data1))
	winsound.PlaySound('temp.wav', winsound.SND_FILENAME|winsound.SND_NOWAIT)


